# coding: utf-8
# Author: An Chao
 
from PIL import Image
import numpy as np

import os
import tkinter as tk
from tkinter import filedialog, messagebox
import threading
from PIL import ImageTk
 
def read_img(img_path):
    '''
    读取图片，并将其转化为包含alpha通道的“RGBA”格式图片。
    img_path：图片路径
    '''
    image=Image.open(img_path)
    image = image.convert('RGBA')
    size = image.size
    image = np.array(image)
    return size, image
 
def get_elesign(size,image):
    '''
    签名是黑色区域，像素值接近0。
    size：输入图片尺寸
    image：numpy array格式的图像
    '''
    points = []
    for j in range(size[0]):
        for i in range(size[1]):
            if image[i][j][0]>100 and image[i][j][1]>100 and image[i][j][2]>100:
                image[i][j][3] = 0
            else:
                image[i][j][0],image[i][j][1],image[i][j][2] = 0,0,0
                points.append((i,j)) 
    return points, image
 
    
 
class SignatureApp:
    def __init__(self, root):
        self.root = root
        self.root.title("手写签名转电子版小工具v1.0.0 By:xianyuwangyou")
        # 计算窗口居中位置
        window_width = 800
        window_height = 600
        screen_width = self.root.winfo_screenwidth()
        screen_height = self.root.winfo_screenheight()
        x = (screen_width - window_width) // 2
        y = (screen_height - window_height) // 2
        self.root.geometry(f"{window_width}x{window_height}+{x}+{y}")
        self.root.resizable(True, True)        

        # 设置中文字体
        self.font = ("SimHei", 10)

        # 创建主框架
        main_frame = tk.Frame(root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # 创建按钮区域
        button_frame = tk.Frame(main_frame)
        button_frame.pack(fill=tk.X, pady=(0, 10))

        self.select_btn = tk.Button(button_frame, text="选择签名图片", command=self.select_image, font=self.font)
        self.select_btn.pack(side=tk.LEFT, padx=5)

        # 添加图片导航按钮
        self.prev_btn = tk.Button(button_frame, text="上一张", command=self.show_previous_image, font=self.font, state=tk.DISABLED)
        self.prev_btn.pack(side=tk.LEFT, padx=5)
        
        self.next_btn = tk.Button(button_frame, text="下一张", command=self.show_next_image, font=self.font, state=tk.DISABLED)
        self.next_btn.pack(side=tk.LEFT, padx=5)

        self.process_btn = tk.Button(button_frame, text="保存电子签名", command=self.save_processed_image, font=self.font, state=tk.DISABLED)
        self.process_btn.pack(side=tk.LEFT, padx=5)

        self.help_btn = tk.Button(button_frame, text="使用说明", command=self.show_usage_instructions, font=self.font)
        self.help_btn.pack(side=tk.LEFT, padx=5)

        # 移除签名留白大小设置

        # 创建图片显示区域
        image_frame = tk.Frame(main_frame)
        image_frame.pack(fill=tk.BOTH, expand=True)

        # 原始图片区域
        original_frame = tk.LabelFrame(image_frame, text="原始图片", font=self.font)
        original_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)

        self.original_img_label = tk.Label(original_frame, text="请选择图片", font=self.font)
        self.original_img_label.pack(fill=tk.BOTH, expand=True)

        # 处理后图片区域
        processed_frame = tk.LabelFrame(image_frame, text="处理后效果", font=self.font)
        processed_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5)

        self.processed_img_label = tk.Label(processed_frame, font=self.font)
        self.processed_img_label.pack(fill=tk.BOTH, expand=True)

        # 状态标签
        self.status_var = tk.StringVar(value="就绪")
        status_label = tk.Label(root, textvariable=self.status_var, bd=1, relief=tk.SUNKEN, anchor=tk.W, font=self.font)
        status_label.pack(side=tk.BOTTOM, fill=tk.X)

        # 初始化变量
        self.image_paths = None
        self.output_dir = None
        self.processed_image = None
        self.processing = False
        self.current_image_index = 0

    def select_image(self):
        self.image_paths = filedialog.askopenfilenames(
            title="选择签名图片",
            filetypes=[("图片文件", "*.jpeg;*.jpg;*.png;*.PNG;*.JPG")]
        )
        self.image_path = self.image_paths[0] if self.image_paths else None
        if self.image_paths:
            count = len(self.image_paths)
            filenames = [os.path.basename(p) for p in self.image_paths[:3]]
            if count > 3:
                filenames.append(f"等{count-3}个文件")
            self.current_image_index = 0
            self.status_var.set(f"已选择{count}张图片: {', '.join(filenames)} (当前: {self.current_image_index+1}/{count})")
            # 显示第一张图片作为预览
            self.display_image(self.image_paths[self.current_image_index], self.original_img_label)
            self.update_nav_buttons()
            self.check_process_ready()
            self.process_image()  # 选择图片后立即处理


    def update_nav_buttons(self):
        if not self.image_paths or len(self.image_paths) <= 1:
            self.prev_btn.config(state=tk.DISABLED)
            self.next_btn.config(state=tk.DISABLED)
        else:
            self.prev_btn.config(state=tk.NORMAL)
            self.next_btn.config(state=tk.NORMAL)
            
    def check_process_ready(self):
        if self.image_paths:
            self.process_btn.config(state=tk.NORMAL)
            self.update_nav_buttons()
        else:
            self.process_btn.config(state=tk.DISABLED)
            self.prev_btn.config(state=tk.DISABLED)
            self.next_btn.config(state=tk.DISABLED)

    def show_previous_image(self):
        if self.image_paths and len(self.image_paths) > 1:
            self.current_image_index = (self.current_image_index - 1) % len(self.image_paths)
            self.display_image(self.image_paths[self.current_image_index], self.original_img_label)
            self.status_var.set(f"已选择{len(self.image_paths)}张图片 (当前: {self.current_image_index+1}/{len(self.image_paths)})")
            # 显示对应索引的处理后图片
            if self.processed_images and 0 <= self.current_image_index < len(self.processed_images):
                self.display_processed_image(self.processed_images[self.current_image_index])
            self.update_nav_buttons()

    def show_next_image(self):
        if self.image_paths and len(self.image_paths) > 1:
            self.current_image_index = (self.current_image_index + 1) % len(self.image_paths)
            self.display_image(self.image_paths[self.current_image_index], self.original_img_label)
            self.status_var.set(f"已选择{len(self.image_paths)}张图片 (当前: {self.current_image_index+1}/{len(self.image_paths)})")
            # 显示对应索引的处理后图片
            if self.processed_images and 0 <= self.current_image_index < len(self.processed_images):
                self.display_processed_image(self.processed_images[self.current_image_index])
            self.update_nav_buttons()

    def display_image(self, img_path, label):
        try:
            image = Image.open(img_path)
            # 调整图像大小以适应标签
            max_size = (350, 250)
            image.thumbnail(max_size, Image.Resampling.LANCZOS)
            photo = ImageTk.PhotoImage(image)
            label.config(image=photo)
            label.image = photo  # 保持引用
            return True
        except Exception as e:
            self.status_var.set(f"图片显示错误: {str(e)}")
            return False

    def display_processed_image(self, processed_img):
        try:
            max_size = (350, 250)
            temp_img = processed_img.copy()
            temp_img.thumbnail(max_size, Image.Resampling.LANCZOS)
            photo = ImageTk.PhotoImage(temp_img)
            self.processed_img_label.config(image=photo)
            self.processed_img_label.image = photo  # 保持引用
            return True
        except Exception as e:
            self.status_var.set(f"处理后图片显示错误: {str(e)}")
            return False

    def process_image(self):
        if not self.image_paths or self.processing:
            return

        self.processing = True
        self.status_var.set("正在处理图片...")
        self.process_btn.config(state=tk.DISABLED)

        # 启动后台线程处理图片
        threading.Thread(target=self._process_in_background, daemon=True).start()

    def _process_in_background(self):
        try:
            # 使用固定偏移量5
            offset = 5

            # 处理图片
            # 循环处理所有选中的图片
            self.processed_images = []
            total_images = len(self.image_paths)
            for i, img_path in enumerate(self.image_paths):
                size, image = read_img(img_path)
                points, image = get_elesign(size, image)
                points = np.array(points).reshape((-1, 2))
                min_value = np.min(points,axis=0)
                x1,y1 = min_value[0]-offset,min_value[1]-offset
                max_value = np.max(points,axis=0)
                x2,y2 = max_value[0]+offset,max_value[1]+offset
                sign_area = image[x1:x2,y1:y2]
                self.processed_images.append(Image.fromarray(sign_area))
                
                # 更新进度
                progress = (i + 1) / total_images * 100
                self.root.after(0, lambda p=progress: self.status_var.set(f"正在处理图片... {p:.1f}%"))
            
            # 显示第一张处理后的图片作为预览
            self.temp_processed_image = self.processed_images[0] if self.processed_images else None

            # 通过主线程更新UI
            self.root.after(0, lambda: self._update_ui_after_processing())
        except Exception as e:
            self.root.after(0, lambda: self._show_error(str(e)))
        finally:
            self.root.after(0, lambda: self._processing_complete())

    def _update_ui_after_processing(self):
        try:
            # 显示临时处理后的图片
            max_size = (350, 250)
            temp_img = self.temp_processed_image.copy()
            temp_img.thumbnail(max_size, Image.Resampling.LANCZOS)
            photo = ImageTk.PhotoImage(temp_img)
            self.processed_img_label.config(image=photo)
            self.processed_img_label.image = photo  # 保持引用
            self.status_var.set("处理完成，请点击'保存电子签名'按钮保存")
        except Exception as e:
            self.status_var.set(f"处理完成，但无法显示结果图片: {str(e)}")

    def _show_error(self, error_msg):
        self.status_var.set(f"处理错误: {error_msg}")

    def _processing_complete(self):
        self.processing = False
        self.current_image_index = 0
        self.process_btn.config(state=tk.NORMAL)

    def show_usage_instructions(self):
        """显示使用说明窗口"""
        help_window = tk.Toplevel(self.root)
        help_window.title("使用说明")
        window_width = 600
        window_height = 450
        screen_width = help_window.winfo_screenwidth()
        screen_height = help_window.winfo_screenheight()
        x = (screen_width - window_width) // 2
        y = (screen_height - window_height) // 2
        help_window.geometry(f"{window_width}x{window_height}+{x}+{y}")
        help_window.resizable(False, False)
        help_window.transient(self.root)
        help_window.grab_set()
        # 设置窗口标题
        help_window.title("使用说明")

        # 创建滚动条
        scrollbar = tk.Scrollbar(help_window)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        # 创建文本框并添加滚动条
        text_widget = tk.Text(help_window, wrap=tk.WORD, yscrollcommand=scrollbar.set, font=self.font, padx=10, pady=10)
        text_widget.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.config(command=text_widget.yview)

        # 使用说明内容
        instructions = """
手写签名转电子版工具使用说明
========================================

功能说明：
本工具可以将手写签名图片转换为透明背景的电子版签名，支持批量处理多张图片。

操作步骤：
1. 点击"选择签名图片"按钮，选择一张或多张签名图片（支持JPG、PNG格式）
2. 工具会自动处理选中的图片，在右侧预览区域显示处理效果
3. 使用"上一张"和"下一张"按钮切换查看不同图片的处理效果
4. 点击"保存电子签名"按钮，将处理后的签名保存为PNG图片

※注意事项：
- 建议使用背景干净、光线充足的签名图片以获得最佳效果
- 签名图片请尽量裁切为手写签名区域，避免包含背景元素
- 单张图片请保证只有1个名字
- 签名区域应尽量清晰，避免过多干扰元素
- 批量处理时，文件名将自动添加序号以区分

========================================

图片处理核心代码来自：
CSDN社区-超超超爱AI
原文链接：
https://blog.csdn.net/weixin_41803339/article/details/105877636

免责声明：
本工具仅用于学习交流，不提供任何形式的技术支持或保证。
用户需自行承担因使用本工具而产生的任何法律责任。
        """
        text_widget.insert(tk.END, instructions)
        text_widget.config(state=tk.DISABLED)  # 设置为只读

    def save_processed_image(self):
        if not hasattr(self, 'processed_images') or not self.processed_images:
            messagebox.showinfo("提示", "没有可保存的签名图片，请先选择并处理图片")
            return
        
        save_path = filedialog.asksaveasfilename(
            defaultextension=".png",
            filetypes=[("PNG图片", "*.png"), ("所有文件", "*.*")],
            title="保存电子签名",
            initialfile="电子签名"
        )
        if save_path:
            try:
                if len(self.processed_images) == 1:
                    self.processed_images[0].save(save_path)
                    self.status_var.set(f"电子签名已保存至: {save_path}")
                    messagebox.showinfo("成功", f"电子签名已保存至: {save_path}")
                else:
                    base, ext = os.path.splitext(save_path)
                    for i, img in enumerate(self.processed_images):
                        img_path = f"{base}_{i+1}{ext}"
                        img.save(img_path)
                    save_dir = os.path.dirname(save_path)
                    self.status_var.set(f"{len(self.processed_images)}个电子签名已保存至: {save_dir}")
                    messagebox.showinfo("成功", f"{len(self.processed_images)}个电子签名已保存完成")
            except Exception as e:
                self.status_var.set(f"保存失败: {str(e)}")
                messagebox.showerror("错误", f"保存失败: {str(e)}")

if __name__ == "__main__":
    root = tk.Tk()
    app = SignatureApp(root)
    root.mainloop()